﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BF.Common
{
    enum TDesMode { dmEncry, dmDecry };
    public class LDESCrypt
    {
        static readonly byte[] BitIP =
        {
         57, 49, 41, 33, 25, 17,  9,  1, 59, 51, 43, 35, 27, 19, 11,  3,
         61, 53, 45, 37, 29, 21, 13,  5, 63, 55, 47, 39, 31, 23, 15,  7,
         56, 48, 40, 32, 24, 16,  8,  0, 58, 50, 42, 34, 26, 18, 10,  2,
         60, 52, 44, 36, 28, 20, 12,  4, 62, 54, 46, 38, 30, 22, 14,  6
        };

        static readonly byte[] BitCP =
        {
         39,  7, 47, 15, 55, 23, 63, 31, 38,  6, 46, 14, 54, 22, 62, 30,
         37,  5, 45, 13, 53, 21, 61, 29, 36,  4, 44, 12, 52, 20, 60, 28,
         35,  3, 43, 11, 51, 19, 59, 27, 34,  2, 42, 10, 50, 18, 58, 26,
         33,  1, 41,  9, 49, 17, 57, 25, 32,  0, 40,  8, 48, 16, 56, 24
        };

        static readonly int[] BitExp =
        {
         31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9,10,
         11,12,11,12,13,14,15,16,15,16,17,18,19,20,19,20,
         21,22,23,24,23,24,25,26,27,28,27,28,29,30,31, 0
        };

        static readonly byte[] BitPM =
        {
         15, 6,19,20,28,11,27,16, 0,14,22,25, 4,17,30, 9,
          1, 7,23,13,31,26, 2, 8,18,12,29, 5,21,10, 3,24
        };

        static readonly byte[,] sBox =
        {
          {
            14,  4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7,
             0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8,
             4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0,
            15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13
          },
         {
            15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10,
             3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5,
             0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15,
            13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9
          },
         {
            10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8,
            13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1,
            13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7,
             1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12
          },
         {
             7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15,
            13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9,
            10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4,
             3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14
          },
         {
             2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9,
            14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6,
             4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14,
            11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3
          },
         {
            12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11,
            10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8,
             9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6,
             4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13
          },
         {
             4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1,
            13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6,
             1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2,
             6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12
          },
         {
            13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7,
             1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2,
             7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8,
             2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11
         }
       };

        static readonly byte[] BitPMC1 =
        {
         56, 48, 40, 32, 24, 16,  8,  0, 57, 49, 41, 33, 25, 17,
          9,  1, 58, 50, 42, 34, 26, 18, 10,  2, 59, 51, 43, 35,
         62, 54, 46, 38, 30, 22, 14,  6, 61, 53, 45, 37, 29, 21,
         13,  5, 60, 52, 44, 36, 28, 20, 12,  4, 27, 19, 11,  3
        };

        static readonly byte[] BitPMC2 =
        {
         13, 16, 10, 23,  0,  4,  2, 27, 14,  5, 20,  9,
         22, 18, 11,  3, 25,  7, 15,  6, 26, 19, 12,  1,
         40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
         43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
        };

        byte[][] SubKey;

        public LDESCrypt()
        {
            SubKey = new byte[16][];
            for (int i = 0; i < SubKey.Length; i++)
            {
                SubKey[i] = new byte[6];
            }
        }

        void initPermutation(byte[] inData)
        {
            byte[] newData = new byte[8];
            for (int i = 0; i < 64; i++)
            {
                if ((inData[BitIP[i] >> 3] & (1 << (7 - (BitIP[i] & 0x07)))) != 0)
                {
                    newData[i >> 3] |= (byte)(1 << (7 - (i & 0x07)));
                }
            }

            Array.Copy(newData, inData, 8);
        }

        void conversePermutation(byte[] inData)
        {
            byte[] newData = new byte[8];
            for (int i = 0; i < 64; i++)
            {
                if ((inData[BitCP[i] >> 3] & (1 << (7 - (BitCP[i] & 0x07)))) != 0)
                {
                    newData[i >> 3] |= (byte)(1 << (7 - (i & 0x07)));
                }
            }

            Array.Copy(newData, inData, 8);
        }

        void expand(byte[] inData, byte[] outData)
        {
            Array.Clear(outData, 0, 6);
            for (int i = 0; i < 48; i++)
            {
                if ((inData[BitExp[i] >> 3] & (1 << (7 - (BitExp[i] & 0x07)))) != 0)
                {
                    outData[i >> 3] |= (byte)(1 << (7 - (i & 0x07)));
                }
            }
        }

        void permutation(byte[] inData)
        {
            byte[] newData = new byte[4];
            for (int i = 0; i < 32; i++)
            {
                if ((inData[BitPM[i] >> 3] & (1 << (7 - (BitPM[i] & 0x07)))) != 0)
                {
                    newData[i >> 3] |= (byte)(1 << (7 - (i & 0x07)));
                }
            }

            Array.Copy(newData, inData, 4);
        }

        byte si(byte s, byte inByte)
        {
            int c = (inByte & 0x20) | ((inByte & 0x1e) >> 1) | ((inByte & 0x01) << 4);
            return (byte)(sBox[s, c] & 0x0f);
        }

        void permutationChoose1(byte[] inData, byte[] outData)
        {
            Array.Clear(outData, 0, 7);
            for (int i = 0; i < 56; i++)
            {
                if ((inData[BitPMC1[i] >> 3] & (1 << (7 - (BitPMC1[i] & 0x07)))) != 0)
                {
                    outData[i >> 3] |= (byte)(1 << (7 - (i & 0x07)));
                }
            }
        }

        void permutationChoose2(byte[] inData, byte[] outData)
        {
            Array.Clear(outData, 0, 6);
            for (int i = 0; i < 48; i++)
            {
                if ((inData[BitPMC2[i] >> 3] & (1 << (7 - (BitPMC2[i] & 0x07)))) != 0)
                {
                    outData[i >> 3] |= (byte)(1 << (7 - (i & 0x07)));
                }
            }
        }

        void cycleMove(byte[] inData, byte bitMove)
        {
            for (int i = 0; i < bitMove; i++)
            {
                inData[0] = (byte)((inData[0] << 1) | (inData[1] >> 7));
                inData[1] = (byte)((inData[1] << 1) | (inData[2] >> 7));
                inData[2] = (byte)((inData[2] << 1) | (inData[3] >> 7));
                inData[3] = (byte)((inData[3] << 1) | ((inData[0] & 0x10) >> 4));
                inData[0] = (byte)(inData[0] & 0x0f);
            }
        }

        static readonly byte[] bitDisplace = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };

        void makeKey(byte[] inKey, byte[][] outKey)
        {
            byte[] outData56 = new byte[7];
            byte[] key28l = new byte[4];
            byte[] key28r = new byte[4];
            byte[] key56o = new byte[7];

            permutationChoose1(inKey, outData56);
            key28l[0] = (byte)(outData56[0] >> 4);
            key28l[1] = (byte)((outData56[0] << 4) | (outData56[1] >> 4));
            key28l[2] = (byte)((outData56[1] << 4) | (outData56[2] >> 4));
            key28l[3] = (byte)((outData56[2] << 4) | (outData56[3] >> 4));
            key28r[0] = (byte)(outData56[3] & 0x0f);
            key28r[1] = (byte)(outData56[4]);
            key28r[2] = (byte)(outData56[5]);
            key28r[3] = (byte)(outData56[6]);

            for (int i = 0; i < 16; i++)
            {
                cycleMove(key28l, bitDisplace[i]);
                cycleMove(key28r, bitDisplace[i]);
                key56o[0] = (byte)((key28l[0] << 4) | (key28l[1] >> 4));
                key56o[1] = (byte)((key28l[1] << 4) | (key28l[2] >> 4));
                key56o[2] = (byte)((key28l[2] << 4) | (key28l[3] >> 4));
                key56o[3] = (byte)((key28l[3] << 4) | (key28r[0]));
                key56o[4] = (byte)(key28r[1]);
                key56o[5] = (byte)(key28r[2]);
                key56o[6] = (byte)(key28r[3]);
                permutationChoose2(key56o, outKey[i]);
            };
        }

        void encry(byte[] inData, byte[] subKey, byte[] outData)
        {
            byte[] outBuf = new byte[6];
            byte[] buf = new byte[8];

            expand(inData, outBuf);
            for (int i = 0; i < 6; i++) outBuf[i] = (byte)(outBuf[i] ^ subKey[i]);
            // outBuf  xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
            buf[0] = (byte)(outBuf[0] >> 2);                               //xxxxxx -> 2
            buf[1] = (byte)(((outBuf[0] & 0x03) << 4) | (outBuf[1] >> 4)); // 4 <- xx xxxx -> 4
            buf[2] = (byte)(((outBuf[1] & 0x0f) << 2) | (outBuf[2] >> 6)); //        2 <- xxxx xx -> 6
            buf[3] = (byte)(outBuf[2] & 0x3f);                             //                    xxxxxx
            buf[4] = (byte)(outBuf[3] >> 2);                               //                           xxxxxx
            buf[5] = (byte)(((outBuf[3] & 0x03) << 4) | (outBuf[4] >> 4)); //                                 xx xxxx
            buf[6] = (byte)(((outBuf[4] & 0x0f) << 2) | (outBuf[5] >> 6)); //                                        xxxx xx
            buf[7] = (byte)(outBuf[5] & 0x3f);                             //                                               xxxxxx
            for (int i = 0; i < 8; i++) buf[i] = si((byte)i, buf[i]);
            for (int i = 0; i < 4; i++) outBuf[i] = (byte)((buf[i * 2] << 4) | buf[i * 2 + 1]);
            permutation(outBuf);
            for (int i = 0; i < 4; i++) outData[i] = outBuf[i];
        }

        // inData, outData 都为 8 Bytes，否则出错
        void desData(TDesMode desMode, byte[] inData, byte[] outData)
        {
            int i, j;
            byte[] temp = new byte[4];
            byte[] buf = new byte[4];

            for (i = 0; i < 8; i++) outData[i] = inData[i];
            initPermutation(outData);
            if (desMode == TDesMode.dmEncry)
            {
                for (i = 0; i < 16; i++)
                {
                    for (j = 0; j < 4; j++) temp[j] = outData[j];                     //temp = Ln
                    for (j = 0; j < 4; j++) outData[j] = outData[j + 4];                //Ln+1 = Rn
                    encry(outData, SubKey[i], buf);                                   //Rn ==Kn==> buf
                    for (j = 0; j < 4; j++) outData[j + 4] = (byte)(temp[j] ^ buf[j]);  //Rn+1 = Ln^buf
                };
                for (j = 0; j < 4; j++) temp[j] = outData[j + 4];
                for (j = 0; j < 4; j++) outData[j + 4] = outData[j];
                for (j = 0; j < 4; j++) outData[j] = temp[j];
            }
            else if (desMode == TDesMode.dmDecry)
            {
                for (i = 15; i >= 0; i--)
                {
                    for (j = 0; j < 4; j++) temp[j] = outData[j];
                    for (j = 0; j < 4; j++) outData[j] = outData[j + 4];
                    encry(outData, SubKey[i], buf);
                    for (j = 0; j < 4; j++) outData[j + 4] = (byte)(temp[j] ^ buf[j]);
                };
                for (j = 0; j < 4; j++) temp[j] = outData[j + 4];
                for (j = 0; j < 4; j++) outData[j + 4] = outData[j];
                for (j = 0; j < 4; j++) outData[j] = temp[j];
            };
            conversePermutation(outData);
        }

        byte[] Redim(byte[] arr, int newSize)
        {
            if (newSize == arr.Length) return arr;
            byte[] newArr = new byte[newSize];
            Array.Copy(arr, 0, newArr, 0, Math.Min(arr.Length, newSize));
            return newArr;
        }

        /**/
        //////////////////////////////////////////////////////////////

        private byte[] EncryBytes(byte[] inData, byte[] keyByte)
        {
            byte[] tmpByte = new byte[8];
            byte[] outByte = new byte[8];

            if ((inData.Length > 0) && (inData[inData.Length - 1] == 0))
            {
                throw new ArgumentException("The last byte is 0.", "inData");
            }
            if (inData.Length % 8 != 0) inData = Redim(inData, (inData.Length + 7) / 8 * 8);
            if (keyByte.Length != 8) keyByte = Redim(keyByte, 8);
            makeKey(keyByte, SubKey);

            byte[] outData = new byte[inData.Length];
            for (int i = 0; i < inData.Length / 8; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    tmpByte[j] = inData[i * 8 + j];
                }
                desData(TDesMode.dmEncry, tmpByte, outByte);
                for (int j = 0; j < 8; j++)
                {
                    outData[i * 8 + j] = outByte[j];
                }
            };

            return outData;
        }

        private byte[] DecryBytes(byte[] inData, byte[] keyByte)
        {
            byte[] tmpByte = new byte[8];
            byte[] outByte = new byte[8];

            if (keyByte.Length != 8) keyByte = Redim(keyByte, 8);
            makeKey(keyByte, SubKey);

            byte[] outData = new byte[(inData.Length + 7) / 8 * 8];
            for (int i = 0; i < inData.Length / 8; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    tmpByte[j] = inData[i * 8 + j];
                }
                desData(TDesMode.dmDecry, tmpByte, outByte);
                for (int j = 0; j < 8; j++)
                {
                    outData[i * 8 + j] = outByte[j];
                }
            };

            int n = outData.Length - 1;
            while (n >= 0 && outData[n] == 0) --n;
            return Redim(outData, n + 1);
        }

        private string EncryStr(string Str, string Key)
        {
            byte[] inData = Encoding.UTF8.GetBytes(Str);
            byte[] keyByte = Encoding.UTF8.GetBytes(Key);
            byte[] tmpByte = EncryBytes(inData, keyByte);
            StringBuilder tmpStr = new StringBuilder();
            foreach (byte b in tmpByte)
            {
                tmpStr.Append((char)b);
            }
            return tmpStr.ToString();
        }

        private string DecryStr(string Str, string Key)
        {
            byte[] inData = new byte[Str.Length];
            for (int i = 0; i < Str.Length; i++)
            {
                inData[i] = (byte)Str[i];
            }
            byte[] keyByte = Encoding.UTF8.GetBytes(Key);
            byte[] tmpByte = DecryBytes(inData, keyByte);
            return Encoding.UTF8.GetString(tmpByte);
        }

        /// <summary>
        /// 加密Hex字符串
        /// </summary>
        /// <param name="Str">原文</param>
        /// <param name="Key">密钥</param>
        /// <returns></returns>
        public string EncryStrHex(string Str, string Key, Boolean bBase = false)
        {
            // 先用Base64进行编码
            if (bBase)
                Str = Convert.ToBase64String(Encoding.Default.GetBytes(Str));

            byte[] inData = Encoding.UTF8.GetBytes(Str);
            byte[] keyByte = Encoding.UTF8.GetBytes(Key);
            byte[] tmpByte = EncryBytes(inData, keyByte);
            StringBuilder tmpStr = new StringBuilder();
            foreach (byte b in tmpByte)
            {
                tmpStr.AppendFormat("{0:X2}", b);
            }
            return tmpStr.ToString();
        }

        /// <summary>
        /// 解密Hex字符串
        /// </summary>
        /// <param name="StrHex">加密的字符串</param>
        /// <param name="Key">密钥</param>
        /// <returns></returns>
        public string DecryStrHex(string StrHex, string Key, Boolean bBase = false)
        {
            if (StrHex.Length % 2 != 0)
            {
                throw new ArgumentException("String length must be even.", "StrHex");
            }
            byte[] inData = new byte[StrHex.Length / 2];
            for (int i = 0; i < StrHex.Length; i += 2)
            {
                inData[i / 2] = (byte)(Uri.FromHex(StrHex[i]) * 16 + Uri.FromHex(StrHex[i + 1]));
            }
            byte[] keyByte = Encoding.UTF8.GetBytes(Key);
            byte[] tmpByte = DecryBytes(inData, keyByte);

            // 用Base64进行解码
            if (bBase)
                return Encoding.Default.GetString(
                        Convert.FromBase64String(Encoding.UTF8.GetString(tmpByte)));
            else
                return Encoding.UTF8.GetString(tmpByte);
        }
    } // End of class Des
}
